package com.duojuhe.aspect;


import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.duojuhe.common.annotation.OperationLog;
import com.duojuhe.common.bean.UserTokenInfoVo;
import com.duojuhe.cache.LoginUserTokenCache;
import com.duojuhe.common.constant.SystemConstants;
import com.duojuhe.common.enums.SystemEnum;
import com.duojuhe.common.enums.log.LogEnum;
import com.duojuhe.common.exception.base.DuoJuHeException;
import com.duojuhe.common.result.ErrorCodes;
import com.duojuhe.common.result.ServiceResult;
import com.duojuhe.common.utils.idgenerator.UUIDUtils;
import com.duojuhe.common.utils.jsonutils.JsonUtils;
import com.duojuhe.common.utils.thread.ThreadUtils;
import com.duojuhe.coremodule.system.entity.SystemLog;
import com.duojuhe.coremodule.system.mapper.SystemLogMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
/**
 * 系统操作日志
 *
 * @author echo
 */
@Slf4j
@Aspect
@Component
public class LogControllerAspect {
    @Resource
    private SystemLogMapper systemLogMapper;

    /**
     * 定义哪些controller需要处理
     */
    @Pointcut("execution(public * com.duojuhe..*.*(..))")
    public void operationLogPoint() {

    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint
     */
    @AfterReturning(value = "(operationLogPoint() && @annotation(operationLog))", returning = "returnResult")
    public void before(final JoinPoint joinPoint, OperationLog operationLog, Object returnResult) throws DuoJuHeException {
        handleLog(joinPoint, operationLog,null, returnResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint
     * @param e 异常
     */
    @AfterThrowing(value = "(operationLogPoint() && @annotation(operationLog))", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog operationLog, Exception e){
        handleLog(joinPoint, operationLog,e, null);
    }


    /**
     * 处理日志
     * @param joinPoint
     * @param e
     * @param returnResult
     */
    protected void handleLog(final JoinPoint joinPoint,OperationLog operationLog, final Exception e, Object returnResult){
        try{
            //未知标识
            String unknownId = SystemConstants.UNKNOWN_ID;
            // 获取当前的用户
            UserTokenInfoVo currentUser = LoginUserTokenCache.getCurrentLoginUserInfoDTO();
            if (unknownId.equals(currentUser.getUserId())){
                return;
            }
            //保存系统日志
            saveSystemLog(currentUser,joinPoint,operationLog, e,returnResult);
        }catch (Exception exp){
            // 记录本地异常日志
            log.error("处理日志切面出现异常信息", exp);
        }
    }

    /**
     * 保存系统日志
     * @param currentUser
     * @param joinPoint
     * @param operationLog
     * @param e
     * @param returnResult
     * @throws Exception
     */
    private void saveSystemLog(UserTokenInfoVo currentUser,JoinPoint joinPoint,OperationLog operationLog, Exception e,
                                     Object returnResult) throws Exception{
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        //当前登录ip
        String loginIp = currentUser.getLoginIp();
        // *========数据库日志=========*//
        String operationStatus = SystemEnum.OPERATION_STATUS.SUCCESS.getKey();
        //日志对象
        SystemLog systemLog = new SystemLog();
        //主键
        systemLog.setLogId(UUIDUtils.getUUID32());
        //创建用户id
        systemLog.setCreateUserId(currentUser.getUserId());
        //创建部门id
        systemLog.setCreateDeptId(currentUser.getCreateDeptId());
        //租户id
        systemLog.setTenantId(currentUser.getTenantId());
        //登录名
        systemLog.setLoginName(currentUser.getLoginName());
        //姓名
        systemLog.setCreateUserName(currentUser.getRealName());
        //创建时间
        systemLog.setCreateTime(new Date());
        //类名
        String className = joinPoint.getTarget().getClass().getName();
        //方法名
        String methodName = joinPoint.getSignature().getName();
        //请求方法名
        systemLog.setMethodName(className + "." + methodName + "()");
        // 设置请求方式
        systemLog.setRequestMethod(request.getMethod());
        //请求模块名称
        systemLog.setModuleName(operationLog.moduleName().getValue());
        //操作类型
        systemLog.setOperationTypeCode(operationLog.operationType().getKey());
        //备注
        systemLog.setRemark(LogEnum.OperationType.getValueByKey(operationLog.operationType().getKey()));
        //请求url
        systemLog.setRequestUrl(request.getRequestURI());
        // 请求ip
        systemLog.setOperationIp(loginIp);
        //操作ip的区域
        systemLog.setIpRegion(currentUser.getIpRegion());
        //返回结果
        if (returnResult!=null){
            if (returnResult instanceof ServiceResult) {
                ServiceResult serviceResult = (ServiceResult) returnResult;
                if (ErrorCodes.SUCCESS.getCode()==serviceResult.getErrorCode()){
                    operationStatus = SystemEnum.OPERATION_STATUS.SUCCESS.getKey();
                }else{
                    operationStatus = SystemEnum.OPERATION_STATUS.FAILED.getKey();
                }
            }
            systemLog.setReturnResult(returnResult.toString());
        }
        if (e != null){
            if (JsonUtils.isJson(e.getMessage())){
                //如果是自定义异常抛出，则不作为异常记录，还是当做普通返回错误
                operationStatus = SystemEnum.OPERATION_STATUS.FAILED.getKey();
                systemLog.setReturnResult(e.getMessage());
            }else{
                systemLog.setExceptionContent(e+"");
                operationStatus = SystemEnum.OPERATION_STATUS.EXCEPTION.getKey();
            }
        }
        //操作状态
        systemLog.setOperationStatusCode(operationStatus);
        //设置请求参数
        systemLog.setRequestParameter(getRequestParameter(joinPoint,request));
        log.info("【系统用户操作日志】{}" ,systemLog.toString());
        // 异步添加到数据库
        ThreadUtils.execute(() -> systemLogMapper.insertSelective(systemLog));
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @throws Exception 异常
     */
    private String getRequestParameter(JoinPoint joinPoint, HttpServletRequest request) throws Exception{
        String params = "";
        Object args = joinPoint.getArgs();
        if (args!=null){
            params = argsArrayToString(joinPoint.getArgs());
        }
        return params;
    }


    /**
     * 忽略敏感属性
     */
    private PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter(){
        return new PropertyPreFilters().addFilter().addExcludes(SystemConstants.EXCLUDE_PROPERTIES);
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray){
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0){
            for (Object o : paramsArray) {
                if (!isFilterObject(o)) {
                    Object jsonObj = JSONObject.toJSONString(o, excludePropertyPreFilter());
                    params.append(jsonObj.toString()).append(" ");
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()){
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        }else if (Collection.class.isAssignableFrom(clazz)){
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        }else if (Map.class.isAssignableFrom(clazz)){
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

}
